package qfssh

import (
	"fmt"
	"io/ioutil"
	"os"
	"path"
	"time"

	"github.com/pkg/sftp"
	qflog "github.com/qf-tech/qftech-base/pkg/log"
	"golang.org/x/crypto/ssh"
)

// Charset def
type Charset string

// const def
const (
	UTF8    = Charset("UTF-8")
	GB18030 = Charset("GB18030")
)

// connect def
func connect(user, password, host string, port int) (*sftp.Client, *ssh.Session, error) {
	var (
		auth         []ssh.AuthMethod
		addr         string
		clientConfig *ssh.ClientConfig
		sshClient    *ssh.Client
		sftpClient   *sftp.Client
		session      *ssh.Session
		err          error
	)
	// get auth method
	auth = make([]ssh.AuthMethod, 0)
	auth = append(auth, ssh.Password(password))
	clientConfig = &ssh.ClientConfig{
		User:            user,
		Auth:            auth,
		Timeout:         30 * time.Second,
		HostKeyCallback: ssh.InsecureIgnoreHostKey(), //ssh.FixedHostKey(hostKey),
	}
	// connet to ssh
	addr = fmt.Sprintf("%s:%d", host, port)
	if sshClient, err = ssh.Dial("tcp", addr, clientConfig); err != nil {
		return nil, nil, err
	}
	// create sftp client
	if sftpClient, err = sftp.NewClient(sshClient); err != nil {
		return nil, nil, err
	}

	// create session
	if session, err = sshClient.NewSession(); err != nil {
		return nil, nil, err
	}

	return sftpClient, session, nil
}

// uploadFile def
func uploadFile(sftpClient *sftp.Client, localFilePath string, remotePath string) {
	srcFile, err := os.Open(localFilePath)
	defer func() {
		_ = srcFile.Close()
	}()
	if err != nil {
		qflog.Sugare.Errorf("os.Open error: %v, %s", err, localFilePath)
		return
	}

	var remoteFileName = path.Base(localFilePath)
	dstFile, err := sftpClient.Create(path.Join(remotePath, remoteFileName))
	defer func() {
		_ = dstFile.Close()
	}()
	if err != nil {
		qflog.Sugare.Errorf("sftpClient.Create error: %v, %s", err, path.Join(remotePath, remoteFileName))
		return
	}

	ff, err := ioutil.ReadAll(srcFile)
	if err != nil {
		qflog.Sugare.Errorf("ReadAll error: %v, %s", err, localFilePath)
		return
	}
	_, _ = dstFile.Write(ff)
	qflog.Sugare.Infof("%s copy file to remote server finished", localFilePath)
}

// uploadDirectory def
func uploadDirectory(sftpClient *sftp.Client, localPath string, remotePath string) {
	localFiles, err := ioutil.ReadDir(localPath)
	if err != nil {
		qflog.Sugare.Errorf("read dir list fail, err: %v", err)
		return
	}
	for _, backupDir := range localFiles {
		localFilePath := path.Join(localPath, backupDir.Name())
		remoteFilePath := path.Join(remotePath, backupDir.Name())
		if backupDir.IsDir() {
			_ = sftpClient.Mkdir(remoteFilePath)
			uploadDirectory(sftpClient, localFilePath, remoteFilePath)
		} else {
			uploadFile(sftpClient, path.Join(localPath, backupDir.Name()), remotePath)
		}
	}
	qflog.Sugare.Infof("%s copy directory to remote server finished", localPath)
}

// DoBackup def
func DoBackup(host string, port int, userName string, password string, localPath string, remotePath string) {
	var (
		err        error
		sftpClient *sftp.Client
		sshSession *ssh.Session
	)
	start := time.Now()
	sftpClient, sshSession, err = connect(userName, password, host, port)
	defer func() {
		_ = sftpClient.Close()
	}()
	if err != nil {
		qflog.Sugare.Error("err: %v", err)
		return
	}

	sshSession.Stdout = os.Stdout
	sshSession.Stderr = os.Stderr
	mkDirCmd := fmt.Sprintf("mkdir -p %s", remotePath)
	err = sshSession.Run(mkDirCmd) //sh 命令路径
	if err != nil {
		qflog.Sugare.Error("run err:%v", err)
		return
	}
	qflog.Sugare.Infof("%s succeed", mkDirCmd)

	_, errStat := sftpClient.Stat(remotePath)
	if errStat != nil {
		qflog.Sugare.Error("%s remote path not exists", remotePath)
		return
	}
	_, err = ioutil.ReadDir(localPath)
	if err != nil {
		qflog.Sugare.Error("%s local path not exists", localPath)
		return
	}
	uploadDirectory(sftpClient, localPath, remotePath)
	elapsed := time.Since(start)
	qflog.Sugare.Infof("elapsed time: %v", elapsed)
}
